<?php

CRequire('DKStorage', 'CError', 'CType');

define ('kDKMySQLError', 'kDKMySQLError');

define ('kDKAutoIncrement', 'kDKAutoIncrement');

CType::SetName(kDKAutoIncrement, CType::OfName(kCIntegerType));

class DKMySQL extends DKStorage {
	var $_Conn;
	
	function &Connect($host, $user, $pass, $db, $classes = null) {
		$error = null;
		$db = &new DKMySQL($host, $user, $pass, $db, $error, $classes);
		
		if (CErr($error))
			return $error;
		else {
			DKStorage::SetDefaultStorage($db);
			return $db;
		}
	}
	
	function Init($host, $user, $pass, $db, &$error, $classes = null) {
		$this->_Classes = $classes;
		$this->_Conn = @mysql_connect($host, $user, $pass);
		
		if (!$this->_Conn) {
			$error = DKMySQL::MySQLError(null);
			return;
		}
		
		if (!(@mysql_select_db($db, $this->_Conn))) {
			$error = DKMySQL::MySQLError(null);
			return;
		}
	}
	
	function _Where($conditions) {	
		$conds = '';
		$first = true;
		
		foreach ($conditions as $col => $expr) {
			if ($col[0] == '*') continue;

			if (!$first) $conds .= ' AND';
			
			if (!is_array($expr))
				$expr = array(kDKEquals, $expr);
			
			switch ($expr[0]) {
				case kDKEquals:
					$conds .= " $col = " . $this->_Quote($expr[1]);
					break;
				case kDKGreaterThan:
					$conds .= " $col > " . $this->_Quote($expr[1]);
					break;
				case kDKLessThan:
					$conds .= " $col < " . $this->_Quote($expr[1]);
					break;
				case kDKGreaterOrEqualTo:
					$conds .= " $col >= " . $this->_Quote($expr[1]);
					break;
				case kDKLessOrEqualTo:
					$conds .= " $col >= " . $this->_Quote($expr[1]);
					break;
				case kDKNotEquals:
					$conds .= " $col <> " . $this->_Quote($expr[1]);
					break;
				case kDKLike:
					$conds .= " $col LIKE " . $this->_Quote($expr[1]);
					break;
				case kDKBetween:
					$conds .= " $col BETWEEN " . $this->_Quote($expr[1]) . " AND " . $this->_Quote($expr[2]);
					break;
			}
			
			$first = false;
		}
		
		
		
		if ($conds == '')
			return null;
		else
			return $conds;
	}
	
	function _Quote(&$val) {
		
		if (is_numeric($val))
			return $val;
		else if (is_string($val))
			return "'" . mysql_real_escape_string($val, $this->_Conn) . "'";
	}
	
	function FindMatching($conditions) {
		if (!isset($conditions[kDKEntity]))
			return CError::MalformedData(null);
		
		$query = "SELECT * FROM {$conditions[kDKEntity]}";
		$where = $this->_Where($conditions);
		
		if (!is_null($where))
			$query .= " WHERE $where";
			
		if (isset($conditions[kDKSortBy])) {
			$query .= " ORDER BY ";
			if (is_string($conditions[kDKSortBy])) {
				$query .= $conditions[kDKSortBy] . " ";
				if (@$conditions[kDKSortAscending])
					$query .= ' ASC';
				else if (@$conditions[kDKSortAscending] === false)
					$query .= ' DESC';
			} else {
				foreach ($conditions[kDKSortBy] as $sb) {
					$query .= " $sb ";
					if (@$conditions[kDKSortAscending])
						$query .= ' ASC';
					else if (@$conditions[kDKSortAscending] === false)
						$query .= ' DESC';
				}
			}
		}
		
		if (isset($conditions[kDKLimit]) &&
			is_finite($conditions[kDKLimit])) {
			$query .= " LIMIT";
			if (isset($conditions[kDKBeginAt]))
				$query .= " {$conditions[kDKBeginAt]},";
			$query .= $conditions[kDKLimit];
		}
		
		
		$result = @mysql_query($query, $this->_Conn);
		if (!$result)
			return DKMySQL::MySQLError(null);
		
		$class = $this->_ClassForEntity($conditions[kDKEntity]);
		$results = array();
		
		while (is_array($row = mysql_fetch_assoc($result))) {
			$x = &new $class();
			foreach ($row as $col => $value) {
				if (is_numeric($col))
					continue;
				$x->SetAttribute($col, $value);
			}
			
			$results[] = &$x;
		}
		
		return $results;
	}
	
	function SetEntityClasses($map) {
		$cl = array();
		$en = array();
		foreach ($map as $entity => $class) {
			$en[strtolower($entity)] = CSymbol($class);
			$cl[CSymbol($class)] = strtolower($entity);
		}
		
		$this->_Classes = $cl;
		$this->_Entities = $en;
	}
	
	var $_Classes = array();
	var $_Entities = array();
	
	function _ClassForEntity($table) {
		$x = @$this->_Classes[strtolower($table)];
		if (is_null($x))
			return $table;
	}
	
	function _EntityForClass($class) {
		$x = @$this->_Entities[CSymbol($class)];
		if (is_null($x))
			return $class;
	}

	function Save(&$record) {
		$cols = "INSERT INTO " . $this->_EntityForClass(get_class($record)) . ' (';
		$vals = '(';
		$updates = '';
		$notFirst = false;
		
		foreach ($record->Attributes() as $name => $value) {
			
			if (is_null($value))
				continue;
			
			if ($notFirst) {
				$cols .= ', ';
				$vals .= ', ';
				$updates .= ', ';
			}
			
			$cols .= $name;
			$vals .= $this->_Quote($value);
			$updates .= "$name = VALUES($name)";
			
			$notFirst = true;
		}
		
		$cols .= ')';
		$vals .= ')';
		
		$query = "$cols VALUES $vals ON DUPLICATE KEY UPDATE $updates";
		
		
		$ret = mysql_query($query, $this->_Conn)? true : DKMySQL::MySQLError(null);
		if (CErr($ret)) return $ret;
		
		$insertid = mysql_insert_id($this->_Conn);
		foreach ($record->AttributesSchema(kDKOnlyIdentifiers) as $name => $type) {
			if ($type == kDKAutoIncrement) {
				$record->SetAttribute($name, $insertid);
				break;
			}
		}
		
		return true;
	}
	
	function Delete(&$record) {
		$entity = $this->_EntityForClass(get_class($record));
		$conds = $record->Attributes(kDKOnlyIdentifiers);
		$query = sprintf("DELETE FROM %s WHERE %s", $entity, $this->_Where($conds));
		
		return mysql_query($query, $this->_Conn)? true : DKMySQL::MySQLError(null);
	}
	
	function MySQLError($reason) {
		return new CError(kDKMySQLError, "A database error occurred when executing the last command or query: " . mysql_error(), $reason);
	}
}

?>